import { Meta } from '@storybook/addon-docs';

<Meta title="Performance/Optimization" id="performance-optimization" />

# Optimizing for Performance and Caveats

Pre-optimizaton can be the root of all evil, however, there are some best practices you can adhere to that will ensure React Data Table is giving you the best possible performance.

# Passing non-primitive props (objects, arrays and functions)

While React Data Table has internal optimizations to try and prevent re-renders on deeper internal components, it's up to you to make sure that you understand how React manages rendering when props/state change as well as how JavaScript determines equality for non-primitives. As a general rule, or if you are experiencing performance issues you should ensure that any non-primitive props passed into React Data Table are not re-created on every render cycle. This is ever important when you have larger data sets or you are passing complex components and columns to `DataTable`.

# Optimizing Class Components

You can typically achieve this by moving props such as objects, arrays, functions or other React components that you pass to React Data Table outside of the `render` method. For cases where you need to memoize [memoize-one](https://github.com/alexreardon/memoize-one) is a great library.

## Examples of Optimizations

The following component will cause React Data Table to fully re-render every time `onSelectedRowsChange` is triggered. Why? Because when `setState` is called it triggers `myComponent` to re-render which by design triggers a re-render on all child components i.e. `DataTable`. But luckily for you React optimally handles this decision on when and how to re-render `DataTable` and a full re-render should not occur **as long as `DataTable` props are the same**.

However, in the example below `columns` changes on every re-render because it's being re-created. This is due to referential equality checking, simply: `columns[] !== columns[]`. In other words, while both instances of `columns` contain the same elements, they are "different" arrays.

**Bad**

```js
...
import React, { Component } from 'react';
import DataTable from 'react-data-table';

class MyComponent extends Component {
  updateState = state => {
    this.setState({ selectedRows: state.selectedRows }); // triggers MyComponent to re-render with new state
  }

  render () { // by design runs on every setState trigger
    // upon re-render columns array is recreated and thus causes DataTable to re-render
    const columns = [....];

    return (
      <DataTable
        data={data}
        columns={columns}
        onSelectedRowsChange={this.updateState}
        selectableRows
      />
    )
  }
}
```

A "solution" could be to declare any field that is a non primitive field outside of the render function so that it does not get recreated on every re-render cycle:

**Good**

```js
...
import React, { Component } from 'react';
import DataTable from 'react-data-table';

const columns = [....]; // is only created once

class MyComponent extends Component {
  updateState = state => {
    this.setState({ selectedRows: state.selectedRows });
  }

  render () {
    return (
      <DataTable
        data={data}
        columns={columns}
        onSelectedRowsChange={this.updateState}
        selectableRows
      />
    )
  }
}

```

But that only works if you don't need to pass component props/methods to the column object. For example, what if you want to attach an event handler to a button in the row using `column.cell`?

```js
const columns = [
  {
    cell: () => <Button raised primary onClick={this.handleAction}>Action</Button>,
    ignoreRowClick: true,
    allowOverflow: true,
    button: true,
  },
  ...
];
```

So how do we attach event handlers to our columns without having to place it in the `render` method and dealing with unnecessary re-renders?

1. Create a `columns` function and pass the arguments needed
2. Memoize the `columns` function

This way, when React checks if `columns` has changed `columns` will instead be the cached result (remember referential equality), thus no unnecessary re-render.

Got it? Let's try this again with the optimal solution using the wonderful memoization library `memoize-one`:

```js
import React, { Component } from 'react';
import memoize from 'memoize-one';
import DataTable from 'react-data-table';

const columns = memoize(handleAction => [
  ...
  {
    cell: () => <Button raised primary onClick={handleAction}>Action</Button>,
    ignoreRowClick: true,
    allowOverflow: true,
    button: true,
  },
  ...
]);

class MyComponent extends Component {
  updateState = state => {
    this.setState({ selectedRows: state.selectedRows });
  }

  render () {
    return (
      <DataTable
        data={data}
        columns={columns(this.updateState)}
        onSelectedRowsChange={this.updateState}
        selectableRows
      />
    );
  }
}
```

Notice that `this.updateState` does not require memoization. That's because `this.updateState` is defined as a class method and therefore only created once.

### Optimizing Functional/Hook Components

Optimizing Hook based components is much easier (or at least much less code). If you're building functional components in React 16.8+ you get access to React Hooks such as `useMemo` and `useCallback`. In this example, simply wrap `columns` in a `useMemo` callback and your `updateState` into `useCallback`:

```js
import React, { useState, useMemo } from 'react';
import DataTable from 'react-data-table';

function MyComponentHook() {
  const [thing, setThing] = useState();
  const handleAction = value => setThing(value);

  // unlike class methods updateState will be re-created on each render pass, therefore, make sure that callbacks passed to onSelectedRowsChange are memoized using useCallback
  const updateState = useCallback(state => console.log(state), []);

	// useMemo will only be created once
  const columns = useMemo(() => [
    ...
    {
      cell: () => <Button raised primary onClick={handleAction}>Action</Button>,
      ignoreRowClick: true,
      allowOverflow: true,
      button: true,
    },
    ...
  ], []);

  return (
    <DataTable
      data={data}
      columns={columns}
      onSelectedRowsChange={updateState}
      selectableRows
    />
  );
}
```
